package com.base.essearch.es.service.impl;

import com.base.essearch.es.service.BaseSearchService;
import com.base.essearch.page.PageDto;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * elasticsearch 搜索引擎
 *
 * @author lr
 * @version 0.1
 * @date 2020/12/13 15:33
 */
@Service
public class BaseSearchServiceImpl<T> implements BaseSearchService<T> {
    private Logger log = LoggerFactory.getLogger(getClass());
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Override
    public List<T> query(String keyword, Class<T> clazz) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(keyword))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                // .withSort(new FieldSortBuilder("createTime").order(SortOrder.DESC))
                .build();

        return elasticsearchTemplate.queryForList(searchQuery, clazz);
    }

    /**
     * 高亮显示
     *
     * @auther: zhoudong
     * @date: 2018/12/13 21:22
     */
    @Override
    public List<Map<String, Object>> queryHit(String keyword, String indexName, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        QueryBuilder matchQuery = createQueryBuilder(keyword, fieldNames);

        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);

        // 设置查询字段
        SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(matchQuery)
                .highlighter(highlightBuilder)
                // 设置一次返回的文档数量，最大值：10000
                .setSize(10000)
                .get();

        // 返回搜索结果
        SearchHits hits = response.getHits();

        return getHitList(hits, 1);
    }

    /**
     * 高亮显示，返回分页
     *
     * @auther: zhoudong
     * @date: 2018/12/18 10:29
     */
    @Override
    public PageDto<Map<String, Object>> queryHitByPage(int pageNum, int pageSize, String keyword, String indexName, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        QueryBuilder matchQuery = createQueryBuilder(keyword, fieldNames);

        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = createHighlightBuilder(fieldNames);

        return pageQueryBuilder(matchQuery, highlightBuilder, pageNum, pageSize, indexName);
    }

    public PageDto<T> pageQueryBuilder(QueryBuilder matchQuery, HighlightBuilder highlightBuilder, int pageNum, int pageSize, String indexName, Class<T> tClass) {
        // 设置查询字段
        SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(matchQuery)
                .highlighter(highlightBuilder)
                .setFrom((pageNum - 1) * pageSize)
                // 设置一次返回的文档数量，最大值：10000
                .setSize(pageNum * pageSize)
                .get();

        // 返回搜索结果
        SearchHits searchHits = response.getHits();

        Long totalCount = searchHits.getTotalHits();
        PageDto<T> pageDto = new PageDto<>(pageNum, pageSize, totalCount.intValue());

        List<T> results = new ArrayList<>();

        //组装结果

        for (SearchHit searchHit : searchHits) {

            Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();

            Map<String, HighlightField> highlightFieldsResult = searchHit.getHighlightFields();

            try {
                T t = tClass.newInstance();
                Field[] declaredFields = t.getClass().getDeclaredFields();
                for (Field field : declaredFields) {
                    field.setAccessible(true);
                    String name = field.getName();
                    if (highlightFieldsResult.get(name) != null) {
                        field.set(t, (highlightFieldsResult.get(name).getFragments()[0]).toString());
                    } else {
                        Object fieldValue = sourceAsMap.get(name);
                        if (fieldValue != null && field.getType().getName().equals(fieldValue.getClass().getTypeName())) {
                            field.set(t, fieldValue);
                        }
                    }

                }
                results.add(t);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        pageDto.setList(results);
        return pageDto;
    }

    public PageDto<Map<String, Object>> pageQueryBuilder(QueryBuilder matchQuery, HighlightBuilder highlightBuilder, int pageNum, int pageSize, String indexName) {
        // 设置查询字段
        SearchResponse response = elasticsearchTemplate.getClient().prepareSearch(indexName)
                .setQuery(matchQuery)
                .highlighter(highlightBuilder)
                .setFrom((pageNum - 1) * pageSize)
                // 设置一次返回的文档数量，最大值：10000
                .setSize(pageNum * pageSize)
                .get();

        // 返回搜索结果
        SearchHits hits = response.getHits();

        Long totalCount = hits.getTotalHits();
        PageDto<Map<String, Object>> pageDto = new PageDto<>(pageNum, pageSize, totalCount.intValue());
        pageDto.setList(getHitList(hits, 1));
        return pageDto;
    }

    /**
     * 构造查询条件
     *
     * @auther: zhoudong
     * @date: 2018/12/18 10:42
     */
    public QueryBuilder createQueryBuilder(String keyword, String... fieldNames) {
        // 构造查询条件,使用标准分词器.
        // matchQuery(),单字段搜索
        return QueryBuilders.multiMatchQuery(keyword, fieldNames)
                .analyzer("ik_max_word")
                .operator(Operator.OR);
    }

    /**
     * 构造高亮器
     *
     * @auther: zhoudong
     * @date: 2018/12/18 10:44
     */
    public HighlightBuilder createHighlightBuilder(String... fieldNames) {
        // 设置高亮,使用默认的highlighter高亮器
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                // .field("productName")
                .preTags("<span style='color:red'>")
                .postTags("</span>");

        // 设置高亮字段
        for (String fieldName : fieldNames) {
            highlightBuilder.field(fieldName);
        }

        return highlightBuilder;
    }

    /**
     * 处理高亮结果
     *
     * @auther: zhoudong
     * @date: 2018/12/18 10:48
     */
    private List<Map<String, Object>> getHitList(SearchHits hits, int size) {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map;
        for (SearchHit searchHit : hits) {
            map = new HashMap<>(16);
            // 处理源数据
            Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
            sourceAsMap.put("score", searchHit.getScore());
            map.put("source", sourceAsMap);
            // 处理高亮数据
            Map<String, Object> hitMap = new HashMap<>(16);
            searchHit.getHighlightFields().forEach((k, v) -> {
                StringBuilder hight = new StringBuilder();
                for (Text text : v.getFragments()) {
                    hight.append(text.string());
                }
                hitMap.put(v.getName(), hight.toString());
            });
            map.put("highlight", hitMap);
            list.add(map);
//            if (list.size() >= size) {
//                break;
//            }
        }
        return list;
    }

    @Override
    public void deleteIndex(String indexName) {
        elasticsearchTemplate.deleteIndex(indexName);
    }
}
